﻿using System;
using System.Linq;
using System.Collections.Generic;
using Bettzueche.RLLibrary.Component;

namespace RLProject1.Model {

    /// <summary>
    /// <para>
    /// MoveCost:
    /// ..........
    /// |-2 -1 -2|
    /// |-1    -1|
    /// |-2 -1 -2|
    /// ''''''''''
    /// </para>
    /// </summary>
    public class GridWorld : IEnvironment<GridState, GridAction, double> {

        #region Inner Struct Type MoveCost
        
        /// <summary>
        /// Value-Type, der zu einem Zustand (Feld) die Kosten zu jeder möglichen Aktion (N,O,S,W,...) angibt.
        /// Bsp:<br />
        /// double cost = moveCost[GridActionTypes.N];
        /// // äquivalent zu
        /// double cost = moveCost.north;
        /// </summary>
        struct MoveCost {
            public double north, northEast, east, southEast, south, southWest, west, northWest;

            public double this[GridAction a] {
                get {
                    switch (a.Direction) {
                        case GridActionTypes.N:
                            return north;
                        case GridActionTypes.NO:
                            return northEast;
                        case GridActionTypes.O:
                            return east;
                        case GridActionTypes.SO:
                            return southEast;
                        case GridActionTypes.S:
                            return south;
                        case GridActionTypes.SW:
                            return southWest;
                        case GridActionTypes.W:
                            return west;
                        case GridActionTypes.NW:
                            return northWest;
                        default:
                            return 0;
                    }
                }
                set {
                    switch (a.Direction) {
                        case GridActionTypes.N:
                            north = value;
                            break;
                        case GridActionTypes.NO:
                            northEast = value;
                            break;
                        case GridActionTypes.O:
                            east = value;
                            break;
                        case GridActionTypes.SO:
                            southEast = value;
                            break;
                        case GridActionTypes.S:
                            south = value;
                            break;
                        case GridActionTypes.SW:
                            southWest = value;
                            break;
                        case GridActionTypes.W:
                            west = value;
                            break;
                        case GridActionTypes.NW:
                            northWest = value;
                            break;
                        default:
                            break;
                    }
                }
            }

            public override string ToString() {
                return "MoveCost[" + north + "(N)," + northEast + "(NO)," + east + "(O)," + southEast + "(SO)," +
                    south + "(S)," + southWest + "(SW)," + west + "(W), " + northWest + "(NW)]";
            }

        }

        #endregion

        //private int _state;
        private int _worldWidth;
        private MoveCost[] _world;

        /// <summary>
        /// Alle möglichen Aktionen von NORTH im Uhrzeigersinn bis NORTHWEST.
        /// </summary>
        public static readonly GridAction[] ACTIONS;

        #region Properties & Indexer

        /// <summary>
        /// Gibt oder setzt die Endzustände dieser Welt.
        /// </summary>
        /// <value>
        /// Array mit den Endzuständen.
        /// </value>
        internal IEnumerable<GridState> Finals { get; set; }
        
        /// <summary>
        /// Der aktuelle Zustand, in dem sich der Agent befindet.
        /// </summary>
        public GridState State { get; set; }

        /// <summary>
        /// Gibt an, ob der aktuelle Zustand ein Endzustand ist.<para>
        /// Geeerbt von <see cref="Bettzueche.RLLibrary.Component.IEnvironment{s,a}">IEnvironment{s,a}</see>.</para>
        /// </summary>
        public bool IsFinal {
            get {
                if (Finals == null)
                    return false;
                return Finals.Contains(this.State);
            }
        }

        /// <summary>
        /// Gibt die Gesamtzahl an Zuständen dieser Weltkomponente.
        /// </summary>
        internal int StateCount {
            get { return _world.Length; }
        }

        /// <summary>
        /// Gibt oder setzt die Bewegungskosten (bzw. Belohnung) zum angegebenen Zustand und Aktion.
        /// </summary>
        /// <param name="state">Zustandsnummer</param>
        /// <param name="action">Aktion</param>
        /// <returns>Kosten der spezifizierten Aktion im angegebenen Zusatand</returns>
        public double this[int state, GridAction action] {
            get { return _world[state][action]; }
            set { _world[state][action] = value; }
        }

        #endregion


        #region Constructors

        /// <summary>
        /// Konstuktor zur Initialisierung der statischen Felder.
        /// </summary>
        static GridWorld() {
            GridActionTypes[] types = (GridActionTypes[])Enum.GetValues(typeof(GridActionTypes));
            ACTIONS = new GridAction[types.Length];
            for (int i = 0; i < types.Length; i++) {
                ACTIONS[i] = new GridAction { Direction = types[i] };
            }
        }

        /// <summary>
        /// Erzeugt eine 10x10 Welt, mit Kosten von -1 für gerade Bewegungen und -sqrt(2)
        /// für diagonale Bewegungen.
        /// </summary>
        GridWorld() : this (10,10,null) {
        }

        /// <summary>
        /// Erzeugt eine Kachelwelt der angegebenen Größe, mit Kosten von -1 für gerade Bewegungen und -sqrt(2)
        /// für diagonale Bewegungen.
        /// </summary>
        public GridWorld(int rows, int cols, params GridState[] finalStates) {
            State = 0;
            initDefault(rows, cols);
            this.Finals = finalStates;
        }

        /// <summary>
        /// Erzeugt eine Kachelwelt der angegebenen Größe, mit Kosten von -1 für gerade Bewegungen und -sqrt(2)
        /// für diagonale Bewegungen.
        /// </summary>
        GridWorld(int rows, int cols) : this(rows,cols,null) {
        }

        #endregion


        #region IEnvironment<int,GridAction,int> Members

        /// <summary>
        /// Tu-nix Methode. (Inits the environment.)
        /// </summary>
        /// <param name="specifications">The specifications.</param>
        /// <exception cref="NotSupportedException">wenn action nicht vom Typ RLProjekt1.Model.GridAction</exception>
        public void Init(string specifications)
        {
        }

        public double DoAction(Bettzueche.RLLibrary.Types.IAction action, out Bettzueche.RLLibrary.Types.IState nextState)
        {
            if (! (action is GridAction))
            {
                throw new NotSupportedException(action.GetType() + "not supported. Use " + typeof(GridAction).ToString() + "instead.");
            }
            return this.DoAction((GridAction)action, out nextState);
        }

        /// <summary>
        /// Führt gegebene Aktion aus und liefert Belohnung und Folgezustand
        /// </summary>
        /// <param name="a">Aktion</param>
        /// <param name="nextState">Referenz auf Folgezustand</param>
        /// <returns>Belohnung</returns>
        public double DoAction(GridAction a, out GridState nextState) {
            int oldState = State; //backup des bisherigen Zustands
            int nState; //temporärer nächster Zustand
            // Folgezustand ermitteln und
            // prüfen, ob Weltrand überschritten wird (=Wand):
            switch (a.Direction) {
                case GridActionTypes.N:
                    if (0 <= State && State < _worldWidth) {
                        nState = State; //nördlicher Rand
                    }
                    else {
                        nState = State - _worldWidth;
                    }
                    break;
                case GridActionTypes.NO:
                    if (0 <= State && State < _worldWidth) {
                        nState = State; //nördlicher Rand
                    }
                    else if (State % _worldWidth == _worldWidth - 1) {
                        nState = State; //östl. Rand
                    }
                    else {
                        nState = State - _worldWidth + 1;
                    }
                    break;
                case GridActionTypes.O:
                    if (State % _worldWidth == _worldWidth - 1) {
                        nState = State; // östl. Rand
                    }
                    else {
                        nState = State + 1;
                    }
                    break;
                case GridActionTypes.SO:
                    if (State >= _world.Length - _worldWidth) {
                        nState = State; // südl. Rand
                    }
                    else if (State % _worldWidth == _worldWidth - 1) {
                        nState = State; // östl. Rand
                    }
                    else {
                        nState = State + _worldWidth + 1;
                    }
                    break;
                case GridActionTypes.S:
                    if (State >= _world.Length - _worldWidth) {
                        nState = State; // südl. Rand
                    }
                    else {
                        nState = State + _worldWidth;
                    }
                    break;
                case GridActionTypes.SW:
                    if (State % _worldWidth == 0) {
                        nState = State; // westl. Rand
                    }
                    else if (State >= _world.Length - _worldWidth) {
                        nState = State; // südl. Rand
                    }
                    else {
                        nState = State + _worldWidth - 1;
                    }
                    break;
                case GridActionTypes.W:
                    if (State % _worldWidth == 0) {
                        nState = State; // westl. Rand
                    }
                    else {
                        nState = State - 1;
                    }
                    break;
                case GridActionTypes.NW:
                    if (0 <= State && State < _worldWidth) {
                        nState = State; //nördlicher Rand
                    }
                    else if (State % _worldWidth == 0) {
                        nState = State; // westl. Rand
                    }
                    else {
                        nState = State - _worldWidth - 1;
                    }
                    break;
                default:
                    nState = State;
                    break;
            }
            State = nState;
            nextState = State;
            return _world[oldState][a];
        }


        /// <summary>
        /// Gibt die ausführbaren Aktionen im angegebenen Zustand.
        /// </summary>
        /// <param name="state">ein Zustand</param>
        /// <returns>Ausführbare Aktionen im Zustand 'state'</returns>
        public ICollection<GridAction> Executables(int state) {
            return ACTIONS;
        }

        #endregion


        #region helper methods

        /// <summary>
        /// Default-Welt zwecks Debugging.
        /// 10x10 Felder. Trägt in jedem Feld die Kosten ein, um auf ein benachbartes Feld zu gehen.
        /// Die Weltränder sind Wand --> Zustand bleibt, Belohnung gibt's aber.
        /// </summary>
        void initDefault(int rows, int cols) {
            _worldWidth = cols;
            _world = new MoveCost[rows * _worldWidth];
            //Standardkosten von -1 eintragen:
            double d1 = -1;
            double d2 = -Math.Sqrt(2 * d1 * d1); //sqrt(2)
            MoveCost cost = new MoveCost {
                south = d1,
                west = d1,
                north = d1,
                east = d1,
                northEast = d2,
                southEast = d2,
                southWest = d2,
                northWest = d2
            };
            for (int i = 0; i < _world.Length; i++) {
                _world[i] = cost;
            }
        }


        /// <summary>
        /// Gibt zum ArrayIndex der Welt den äquivalenten Zeilenindex.
        /// </summary>
        /// <param name="worldIdx"></param>
        /// <returns></returns>
        internal int getRowIdx(int worldIdx) {
            return worldIdx / _worldWidth;
        }

        internal int getColIdx(int worldIdx) {
            return worldIdx % _worldWidth;
        }

        #endregion
    }
}
